#include "Shader.hh"

#include "common/stl.h"
#include "tools/constants.hh"


//{ Ctor - Dtor - Getter - Setter
Shader::Shader(std::string v_Filename, std::string f_Filename)
    : _vId(0),
      _fId(0),
      _programId(glCreateProgram()),
      _vFilename(v_Filename),
      _fFilename(f_Filename)
{
}

// autobuild filenames: add prefix && suffix .{vert,frag}
Shader::Shader(std::string basename)
    : _vId(0),
      _fId(0),
      _programId (glCreateProgram()),
      _vFilename(SHADERS_PATH_PREFIX + basename + ".vert"),
      _fFilename(SHADERS_PATH_PREFIX + basename + ".frag")
{
}


Shader::~Shader()
{
    //dtor
    destroy();
}


GLuint Shader::getProgramId()
{
    return _programId;
}
//} end Ctor - Dtor - Getter - Setter


bool Shader::init()
{
    _vId = glCreateShader(GL_VERTEX_SHADER);
    _fId = glCreateShader(GL_FRAGMENT_SHADER);

    #ifdef DEBUG // testing createShader return values
        if (!_vId)
        {
            std::string msg = "Shader::init: Unable to create VERTEX shader ";
            std::cerr << msg << std::endl;
            return false;
        }

        if (!_fId)
        {
            std::string msg = "Shader::init: Unable to create FRAGMENT shader ";
            std::cerr << msg << std::endl;
            return false;
        }
    #endif

    return (load() && compile() && execute());
}


bool Shader::load()
{
    ////  std::string str((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>());

    #ifdef DEBUG // testing file existence
    std::ifstream stream;

    stream.open(_vFilename.c_str());
    if (!stream)
    {
        std::cerr << "Shader::load: Unable to open " << _vFilename << std::endl;
        stream.close();
        return false;
    }
    stream.close();

    stream.open(_fFilename.c_str());
    if (!stream)
    {
        std::cerr << "Shader::load: Unable to open " << _fFilename << std::endl;
        stream.close();
        return false;
    }
    stream.close();
    #endif


    // vertex shader
    std::ifstream v_ifile(_vFilename.c_str());
    while (v_ifile.good())
    {
      std::string line;
      std::getline(v_ifile, line);
      _vSrc.append(line + '\n');
    }

    // fragment shader
    std::ifstream f_ifile(_fFilename.c_str());
    while (f_ifile.good())
    {
      std::string line;
      std::getline(f_ifile, line);
      _fSrc.append(line + '\n');
    }

    return true;
}



bool Shader::compile()
{
    std::string vStr = _vSrc;
    std::string fStr = _fSrc;

    glShaderSource(_vId, 1, (const GLchar**) &vStr, NULL);
    glShaderSource(_fId, 1, (const GLchar**) &fStr, NULL);

    glCompileShader(_vId);
    glCompileShader(_fId);

    #ifdef DEBUG
        GLint status;
        glGetShaderiv(_vId, GL_COMPILE_STATUS, &status);

        if (status != GL_TRUE)
        {
            GLint log_size;
            glGetShaderiv(_vId, GL_INFO_LOG_LENGTH, &log_size);
            char* log = new char[log_size + 1];

            glGetShaderInfoLog(_vId, log_size, &log_size, log);
            std::cerr << "Shader::compile: shader " << _vFilename
                      << " failed with: " << log << std::endl;

            delete log;
            destroy();

            return false;
        }


        glGetShaderiv(_fId, GL_COMPILE_STATUS, &status);
        if (status != GL_TRUE)
        {
            GLint log_size;
            glGetShaderiv(_fId, GL_INFO_LOG_LENGTH, &log_size);
            char* log = new char[log_size + 1];

            glGetShaderInfoLog(_fId, log_size, &log_size, log);
            std::cerr << "Shader::compile: shader " << _fFilename
                      << " failed with: " << log << std::endl;

            delete log;
            destroy();

            return false;
        }
    #endif

    return true;
}


bool Shader::execute()
{
    glAttachShader(_programId, _vId);
    glAttachShader(_programId, _fId);

    // cd rm shader

    glLinkProgram(_programId);

    #ifdef DEBUG
        GLint status;
        glGetProgramiv(_programId, GL_LINK_STATUS, &status);
        if (status != GL_TRUE)
        {
            GLint log_size;
            glGetProgramiv(_programId, GL_INFO_LOG_LENGTH, &log_size);
            char* log = new char[log_size + 1];

            glGetProgramInfoLog(_programId, log_size, &log_size, log);
            std::cerr << "Shader::execute: linkage error: " << log << std::endl;

            delete log;
            destroy();

            return false;
        }

        glValidateProgram(_programId);
        glGetProgramiv(_programId, GL_VALIDATE_STATUS, &status);
        if (status != GL_TRUE)
        {
        	GLint log_size;
        	glGetProgramiv(_programId, GL_INFO_LOG_LENGTH, &log_size);
			char* log = new char[log_size + 1];

			glGetProgramInfoLog(_programId, log_size, &log_size, log);
			std::cerr << "Shader::execute: validation error: " << log << std::endl;

			delete log;
			destroy();

			return false;
        }
    #endif

    return true;
}


void Shader::destroy()
{
    glUseProgram(0); // drop shader
    glDetachShader(_programId, _vId); // useless? done @glDeleteProgram
    glDetachShader(_programId, _fId); // useless? done @glDeleteProgram
    glDeleteProgram(_programId);
    glDeleteShader(_vId);
    glDeleteShader(_fId);
    _vId = 0;
    _fId = 0;

//    delete _vSrc;
//    delete _fSrc;
}
